Skip to main content

6.3 - Querying the Game - Checking for Pathing and Abilities

Your bot's standard senses (self.state and its helpers) provide a snapshot of what is. But to make truly intelligent decisions, your bot needs to ask what could be. Can a unit get from A to B? Is an ability actually ready to use? Answering these questions requires querying the game engine for a specific calculation.

Queries are async functions that send a request to the StarCraft II engine and await a response. They are essential tools for moving beyond simple reactive logic to proactive, error-proof planning.

The Query System

Think of a query as a direct question to the game engine's internal rulebook.

+------------------+
| | 1. "Can a Marine walk from my base to this island?"
| Your Python Bot | --------------------------------------------------> +---------------+
| (The Querier) | | |
| | 2. "No, the path distance is None." | SC2 Game |
| | <-------------------------------------------------- | Engine |
+------------------+ | (The Oracle) |
+---------------+

This request-response cycle allows your bot to validate its plans before committing resources.


The Three Essential Queries

QueryKey FunctionWhat It AsksWhen to Use It
Placementawait self.find_placement(...)"Where is a valid spot to build this structure?"Always. Before every self.build command to ensure the location is legal.
Pathingawait self.client.query_pathing(...)"Is there a walkable ground path from point A to point B?"Before sending a ground unit to a distant location, especially a potential expansion, to avoid "island" traps.
Abilityawait self.get_available_abilities(...)"Which abilities can this specific unit use right now?"Always. Before casting any ability with a cooldown to ensure it's not on cooldown. This is more reliable than just checking energy.

A Developer's Checklist for Using Queries

  • 1. Identify the Question: What do I need to know before I act? (e.g., "Is this expansion on an island?")
  • 2. Choose the Right Query: Select the function that answers your question (query_pathing, get_available_abilities, etc.).
  • 3. await the Response: Since queries are network requests, you must await the result.
  • 4. Handle the Response: The query might return None or an empty list if the answer is negative. Your code must handle this gracefully.
    • if path_distance is None:
    • if AbilityId.STIMPACK in available_abilities:
  • 5. Act on the Verified Information: Only issue your command after the query has confirmed it's a valid action.

Code Example: The "Inquisitive" Bot

This bot demonstrates how to use queries to make its decisions more robust. It will use a pathing query to verify that its next expansion is reachable and an ability query to ensure its Stimpack command is valid.

# inquisitive_bot.py

from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.ability_id import AbilityId

class InquisitiveBot(BotAI):
"""A bot that uses queries to make smarter decisions."""

async def on_step(self, iteration: int):
await self.manage_expansion()
await self.manage_stimpack()
# Helper to build an army for the demo.
await self.build_army()

async def manage_expansion(self):
"""Uses a pathing query to safely expand."""
# Only try to expand if we have 1 base and can afford it.
if len(self.townhalls) == 1 and self.can_afford(UnitTypeId.COMMANDCENTER):
# Find the location of the next expansion spot.
next_expansion_location = await self.get_next_expansion()
if not next_expansion_location:
return

# QUERY: Can a worker walk from our start to the expansion?
path_distance = await self.client.query_pathing(self.start_location, next_expansion_location)

# HANDLE RESPONSE:
if path_distance is None:
# The path is blocked (e.g., an island). Do not expand.
print(f"WARNING: Expansion at {next_expansion_location} is unreachable by ground.")
else:
# The path is clear. Proceed with the expansion.
print(f"INFO: Expansion is reachable (Distance: {path_distance:.2f}). Expanding.")
await self.expand_now()

async def manage_stimpack(self):
"""Uses an ability query to safely use Stimpack."""
# Find marines near enemies.
marines_in_danger = self.units(UnitTypeId.MARINE).filter(
lambda m: self.enemy_units.closer_than(10, m).exists
)

for marine in marines_in_danger:
# QUERY: What abilities can this specific marine use right now?
available_abilities = await self.get_available_abilities(marine)
# HANDLE RESPONSE: Check if Stimpack is in the list.
if AbilityId.STIMPACK in available_abilities:
print(f"INFO: Marine {marine.tag} is using Stimpack.")
marine(AbilityId.STIMPACK)

async def build_army(self):
"""A simple helper method to produce units for the demo."""
if self.supply_left < 2 and not self.already_pending(UnitTypeId.SUPPLYDEPOT):
if self.can_afford(UnitTypeId.SUPPLYDEPOT):
await self.build(UnitTypeId.SUPPLYDEPOT, near=self.start_location.towards(self.game_info.map_center, 5))
if not self.structures(UnitTypeId.BARRACKS).exists:
if self.can_afford(UnitTypeId.BARRACKS):
await self.build(UnitTypeId.BARRACKS, near=self.start_location.towards(self.game_info.map_center, 8))
elif self.structures(UnitTypeId.BARRACKS).ready.idle.exists and self.can_afford(UnitTypeId.MARINE):
self.train(UnitTypeId.MARINE)


if __name__ == "__main__":
run_game(
maps.get("BlackburnAIE"),
[
Bot(Race.Terran, InquisitiveBot()),
Computer(Race.Zerg, Difficulty.Medium)
],
realtime=True,
)